<?php
/**
 * @link https://ym2.cleverstone.top
 * @copyright Copyright (c) 2020 Yii Manager Software LLC
 */

namespace builder\form;

use Yii;
use yii\web\View;
use yii\web\AssetBundle;
use yii\base\InvalidArgumentException;
use builder\helper\App;
use builder\helper\H5;
use builder\helper\Url;
use builder\helper\RegEx;
use builder\base\Builder;
use builder\base\ViewBuilderInterface;
use builder\form\util\control\Control;
use builder\form\util\sredirect\SrType;
use builder\base\UndefinedParamsException;
use builder\form\util\control\dctrl\items\Dcontrol;

/**
 * 表单构建器
 * @property bool $partial 是否是局部继承, 默认false
 * @property string $submitUrl 表单提交链接, 默认当前URL
 * @property string $title 表单标题
 * @property array $rootLayout 表单根布局
 * @property array $footerLayout 表单尾部布局
 * @property int $groupCol 表单组栅栏格宽
 * @property array $groupLayout 表单组布局
 * @property string $groupComment 表单组注释
 * @property-write  array $formControl 表单组控件项
 * @property string $requiredStyle 必填项标记风格 mark:星号标记 text:文本标记
 * @property-read array $footerBtn 表单底部按钮
 * @property-write string $submitBtn 显示表单提交按钮
 * @property-write string $resetBtn 显示表单重置按钮
 * @property-write string $closeBtn 显示弹窗关闭按钮
 * @property-write string $backBtn 显示页面返回按钮
 * @property-write array $extraBtn 设置额外的按钮
 * @property bool $autoClose 是否提交后自动关闭表单, 默认false
 * @property bool $autoBack 是否提交后自动返回, 默认false
 * @property array $submitConfirm 设置提交确认框
 * @property array $submittedCalls 表单提交后的回调, 空数组代表不回调
 * @property array $submittedRedirect 表单提交后重定向, 空数组代表无操作
 * @property array $extraJs 设置额外的Js
 * @property array $extraCss 设置额外的Css
 * @property array $extraAssetBundle 设置额外的静态资源包
 * @author cleverstone
 * @since ym2.0
 */
class FormBuilder extends Builder implements ViewBuilderInterface
{
    use FormTrait;

    /**
     * The location of registered html or text.
     * This means the location is in the form head section.
     */
    const POS_HEAD = 1;

    /**
     * The location of registered html or text.
     * This means the location is at the end of the form section.
     */
    const POS_END = 2;

    /**
     * @var bool 是否局部继承
     * @see $partial
     * @see setPartial()
     * @see getPartial()
     */
    private $_partial = false;

    /**
     * @var string 提交链接
     * @see $submitUrl
     * @see setSubmitUrl()
     * @see getSubmitUrl()
     */
    private $_submitUrl;

    /**
     * @var string 表单标题
     * @see $title
     * @see setTitle()
     * @see getTitle()
     */
    private $_title;

    /**
     * @var array 设置根布局
     * @see $rootLayout
     * @see setRootLayout()
     * @see getRootLayout()
     */
    private $_rootLayout = [];

    /**
     * @var array 表单尾部布局
     * @see $footerLayout
     * @see setFooterLayout()
     * @see getFooterLayout()
     */
    private $_footerLayout = [];

    /**
     * @var string 表单分组标题
     *@see setGroup()
     */
    private $_groupTitle = '';

    /**
     * @var string 表单分组字段
     * @see setGroup()
     */
    private $_groupField = 'default';

    /**
     * @var int 表单组栅栏格宽度
     * @see $groupCol
     * @see setGroupCol()
     * @see getGroupCol()
     */
    private $_groupCol = 12;

    /**
     * 表单组布局
     * @var array
     * @see $groupLayout
     * @see setGroupLayout()
     * @see getGroupLayout()
     */
    private $_groupLayout = [];

    /**
     * 表单组注释
     * @var string
     * @see $groupComment
     * @see setGroupComment()
     * @see getGroupComment()
     */
    private $_groupComment = '';

    /**
     * 表单内容
     * @var array
     * - key string 分组字段
     * - value array
     *
     * 支持的value有：
     * - title string 分组名称
     * - layout int 组布局
     * - class string 组ui
     * - style string 组样式
     * - attribute string 组属性
     * - comment string 组注释
     * - controls array 组控件内容
     * ****** 隐藏项 ******
     * - __id string HTML ID
     * - __scope string angular scope
     *
     * 支持的controls有：
     * - key string 控件字段
     * - value array
     *
     * 支持的controls.value有：
     * 查看util/control
     *
     * ```php
     * $_body = [
     *     'default' => [
     *          'title' => '',
     *          'layout' => '12',
     *          'class' => '',
     *          'style' => '',
     *          'attribute' => '',
     *          'comment' => '',
     *          'controls' => [
     *              'field1' => [
     *                  'control' => 'text', // 控件名称
     *                  'label' => '文本',
     *                  'placeholder' => '请输入',
     *                  'default' => '',
     *                  'required' => true,
     *                  'comment' => '',
     *                  'layout' => 12,
     *                  'style' => '',
     *                  'class' => '',
     *                  'attribute' => '',
     *                  '__id' => 'default__field1',
     *                  '__scope' => 'default.field1',
     *              ],
     *              'field2' => [
     *                  //...
     *              ]
     *          ],
     *      ]
     * ];
     *
     * ```
     * @see $formControl
     * @see setFormControl()
     * @see builder/form/util/control/*
     */
    private $_body = [];

    /**
     * @var array 表单内容JS选项
     * @see formatControls()
     */
    private $_bodyJsOptions = [];

    /**
     * @var array 表单触发器
     * @see formatControls()
     */
    private $_formTrigger = [];

    /**
     * @var array 表单值连接
     * @see formatControls()
     */
    private $_formValueLink = [];

    /**
     * @var array 表单动态select
     * @see formatControls()
     */
    private $_formDynamicSelect = [];

    /**
     * @var array 表单值监听的字段模型
     * @see formatControls()
     */
    private $_formWatchScope = [];

    /**
     * @var array 表单动态控件项
     * @see formatControls()
     */
    private $_formDynamicControl = [];

    /**
     * @var string 必填项标记风格 mark:星号标记 text:文本标记
     * @see $requiredStyle
     * @see setRequiredStyle()
     * @see getRequiredStyle()
     */
    private $_requiredStyle = 'mark';

    /**
     * @var array 表单切点
     * @see registerPointcut()
     * @see beginForm()
     * @see endForm()
     */
    private $_pointcutBody = [];

    /**
     * @var array 底部按钮
     * @see $footerBtn
     */
    private $_footerBtn = [];

    /**
     * @var bool 是否提交后自动关闭表单, 默认true
     * @see $autoClose
     * @see setAutoClose()
     * @see getAutoClose()
     */
    private $_autoClose = false;

    /**
     * @var bool 是否提交后自动返回, 默认false
     * @see $autoBack
     * @see setAutoBack()
     * @see getAutoBack()
     */
    private $_autoBack = false;

    /**
     * @var array 提交确认框
     * @see $submitConfirm
     * @see setSubmitConfirm()
     * @see getSubmitConfirm()
     */
    private $_submitConfirm = [
        'show' => false,
        'title' => '',
        'content' => '是否确认提交?',
    ];

    /**
     * @var array 表单自动提交后的回调,空数组代表不回调
     * - key 回调窗口
     *   self: 当前窗口
     *   parent: 父级窗口
     *   parent.parent.*: 祖先窗口
     *
     * - value 表单提交后的回调ID(构建器ID或自定义回调方法名称)
     *   构建器ID: 指定回调;如:表格行操作和工具栏操作中的modal表单, 如果回调, 则需要传入该表格的ID
     *   自定义回调方法名称: 指定回调;window.ym2CbkExport对象中的调用方法名称
     *   value为空数组: 回调所有;代表回调window.ym2CbkExport中的所有方法
     *
     * @see $submittedCalls
     * @see setSubmittedCalls()
     * @see getSubmittedCalls()
     */
    private $_submittedCalls = [];

    /**
     * @var array 表单提交后重定向, 空数组代表无操作
     * ========= 公共参数 =========
     * - type string 类型,枚举,支持modal和page
     * - route string 路由
     * - params array 参数
     * ========= modal参数 =========
     * - width string modal宽,modal有效
     * - height string modal高,modal有效
     * - title string modal标题,modal有效
     * - closeBtn boolean modal底部关闭按钮,modal有效
     * ========= page参数 =========
     * - target string 跳转目标,page有效
     * - window string JS窗口对象,page有效
     *
     * @see $submittedRedirect
     * @see setSubmittedRedirect()
     * @see getSubmittedRedirect()
     */
    private $_submittedRedirect = [];

    /**
     * @var array 额外的JS
     * ```php
     * $form->extraCss = <<<JS
     * console.log("测试一下 [setExtraCss] 方法;")
     * JS;
     *
     * ```
     * @see $extraJs
     * @see setExtraJs()
     * @see getExtraJs()
     */
    private $_extraJs = [];

    /**
     * @var array 额外的CSS
     * ```php
     * $form->extraCss = <<<CSS
     * body{background-color: #fff;}
     * CSS;
     *
     * ```
     * @see $extraCss
     * @see setExtraCss()
     * @see getExtraCss()
     */
    private $_extraCss = [];

    /**
     * @var array 额外的静态资源包
     * ```php
     * $form->extraAssetBundle = MemberAsset::class;
     *
     * ```
     * @see $extraAssetBundle
     * @see setExtraAssetBundle()
     * @see getExtraAssetBundle()
     */
    private $_extraAssetBundle = [];

    /**
     * 实例化
     * @param array $config
     * @return FormBuilder
     */
    public static function instance($config = [])
    {
        return new static($config);
    }

    /**
     * 设置视图局部继承
     * @param bool $boolean
     * @return $this
     */
    public function setPartial($boolean = true)
    {
        $this->_partial = $boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getPartial()
    {
        return $this->_partial;
    }

    /**
     * 设置表单提交URL
     * @param string|array $route
     * @see Url::to()
     * @return $this
     */
    public function setSubmitUrl($route)
    {
        if (
            is_array($route)
            && (
                !isset($route[0])
                || $route[0] === ''
            )
        ) {
            unset($route[0]);
            $this->_submitUrl = Url::current($route, '');
        } else {
            $this->_submitUrl = Url::to($route);
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getSubmitUrl()
    {
        if (empty($this->_submitUrl)) {
            return Url::current([], '');
        }

        return $this->_submitUrl;
    }

    /**
     * 设置表单标题
     * @param string $title 表单标题
     * @return $this
     */
    public function setTitle($title)
    {
        $this->_title = (string)$title;
        return $this;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->_title;
    }

    /**
     * 设置根布局
     * - class array|string
     * - style array|string
     * @param array $options
     * @return $this
     */
    public function setRootLayout(array $options)
    {
        if (!empty($options['class'])) {
            $this->formatUiClass($options['class']);
        } else {
            $options['class'] = '';
        }

        if (!empty($options['style'])) {
            $this->formatStyle($options['style']);
        } else {
            $options['style'] = '';
        }

        if (!empty($options['attribute'])) {
            $this->formatHtmlAttribute($options['attribute']);
        } else {
            $options['attribute'] = '';
        }

        $this->_rootLayout = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getRootLayout()
    {
        if (empty($this->_rootLayout)) {
            return [
                'style' => '',
                'class' => '',
                'attribute' => '',
            ];
        }

        return $this->_rootLayout;
    }

    /**
     * 设置表单尾部布局
     * - class array|string
     * - style array|string
     * - attribute array
     * @param array $options
     * @return $this
     */
    public function setFooterLayout(array $options)
    {
        if (!empty($options['class'])) {
            $this->formatUiClass($options['class']);
        } else {
            $options['class'] = 'text-right';
        }

        if (!empty($options['style'])) {
            $this->formatStyle($options['style']);
        } else {
            $options['style'] = '';
        }

        if (!empty($options['attribute'])) {
            $this->formatHtmlAttribute($options['attribute']);
        } else {
            $options['attribute'] = '';
        }

        $this->_footerLayout = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getFooterLayout()
    {
        if (empty($this->_footerLayout)) {
            return [
                'class' => 'text-right',
                'style' => '',
                'attribute' => '',
            ];
        }

        $footerLayout = $this->_footerLayout;
        if (empty($footerLayout['class'])) {
            $footerLayout['class'] = 'text-right';
        }

        return $footerLayout;
    }

    /**
     * 初始化表单分组属性
     */
    private function _initFormGroupOptions()
    {
        $this->_groupCol = 12;
        $this->_groupLayout = [
            'style' => '',
            'class' => '',
            'attribute' => '',
        ];
        $this->_groupComment = '';
    }

    /**
     * 设置表单分组
     * @param string $title 分组名称
     * @param string $field 分组字段（用户表单接收）
     * @return $this
     */
    public function setGroup($title, $field)
    {
        if (!strcasecmp($field, 'default')) {
            throw new InvalidArgumentException('分组字段不能是构建器分组保留字段【default】！');
        }

        $this->_groupField = (string)$field;
        $this->_groupTitle = (string)$title;

        $this->_initFormGroupOptions();

        return $this;
    }

    /**
     * 表单组栅栏格宽
     * @param int $col
     * @return $this
     */
    public function setGroupCol($col)
    {
        $this->_groupCol = (int)$col;
        return $this;
    }

    /**
     * @return int
     */
    public function getGroupCol()
    {
        return $this->_groupCol;
    }

    /**
     * 表单组注释
     * @param string $comment
     * @return $this
     */
    public function setGroupComment($comment)
    {
        $this->_groupComment = (string)$comment;
        return $this;
    }

    /**
     * @return string
     */
    public function getGroupComment()
    {
        return $this->_groupComment;
    }

    /**
     * 设置表单组布局
     * @param array $options
     * @return $this
     */
    public function setGroupLayout(array $options)
    {
        if (!empty($options['class'])) {
            $this->formatUiClass($options['class']);
        } else {
            $options['class'] = '';
        }

        if (!empty($options['style'])) {
            $this->formatStyle($options['style']);
        } else {
            $options['style'] = '';
        }

        if (!empty($options['attribute'])) {
            $this->formatHtmlAttribute($options['attribute']);
        } else {
            $options['attribute'] = '';
        }

        $this->_groupLayout = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getGroupLayout()
    {
        if (empty($this->_groupLayout)) {
            return [
                'style' => '',
                'class' => '',
                'attribute' => '',
            ];
        }

        return $this->_groupLayout;
    }

    /**
     * 注册表单切点
     * @param string $content 内容
     * @param int $pos 切点
     * @return $this
     */
    public function registerPointcut($content, $pos = self::POS_HEAD)
    {
        $this->_pointcutBody[$pos][] = (string)$content;
        return $this;
    }

    /**
     * 设置表单提交按钮
     * @param string $title
     * @return $this
     */
    public function setSubmitBtn($title = '立即提交')
    {
        $this->_footerBtn['submit'] = [
            'title' => $title,
            'content' => '',
        ];
        return $this;
    }

    /**
     * 设置表单重置按钮
     * @param string $title
     * @return $this
     */
    public function setResetBtn($title = '重置')
    {
        $this->_footerBtn['reset'] = [
            'title' => $title,
            'content' => '',
        ];
        return $this;
    }

    /**
     * 设置弹窗关闭按钮
     * @param string $title
     * @return $this
     */
    public function setCloseBtn($title = '关闭')
    {
        $this->_footerBtn['close'] = [
            'title' => $title,
            'content' => '',
        ];
        return $this;
    }

    /**
     * 设置页面返回按钮
     * @param string $title
     * @return $this
     */
    public function setBackBtn($title = '返回')
    {
        $this->_footerBtn['back'] = [
            'title' => $title,
            'content' => '',
        ];
        return $this;
    }

    /**
     * 设置额外的按钮
     * @param array $extraBtn
     * @return $this
     */
    public function setExtraBtn(array $extraBtn)
    {
        foreach ($extraBtn as $item) {
            $this->_footerBtn[] = [
                'title' => '',
                'content' => $item,
            ];
        }
        return $this;
    }

    /**
     * @return array
     */
    public function getFooterBtn()
    {
        return $this->_footerBtn;
    }

    /**
     * 表单自动提交后的回调, 空数组代表不回调
     * @param array $callback
     * @return $this
     */
    public function setSubmittedCalls(array $callback = ['parent' => null])
    {
        foreach ($callback as $window => &$item) {
            if (empty($item)) {
                $item = [];
            } else {
                $item = (array)$item;
            }
        }

        $this->_submittedCalls = $callback;
        return $this;
    }

    /**
     * @return array
     */
    public function getSubmittedCalls()
    {
        return $this->_submittedCalls;
    }

    /**
     * 设置提交后回调窗口
     * @param array|\Closure|SrType $options
     * @return $this
     * @throws UndefinedParamsException
     * @throws \ReflectionException
     */
    public function setSubmittedRedirect($options)
    {
        if ($options instanceof SrType) {
            $options = $options->_toArray();
        } elseif ($options instanceof \Closure) {
            $options = $this->closureDi($options, SrType::class);
            if (!is_array($options) && $options instanceof SrType) {
                $options = $options->_toArray();
            }
        }

        if (!is_array($options)) {
            throw new InvalidArgumentException('Expected parameter [options] is array, But the others are given. ');
        }

        $this->_submittedRedirect = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getSubmittedRedirect()
    {
        return $this->_submittedRedirect;
    }

    /**
     * 是否提交后自动关闭表单, 默认true
     * @param bool $boolean
     * @return $this
     */
    public function setAutoClose($boolean = true)
    {
        $this->_autoClose = $boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getAutoClose()
    {
        return $this->_autoClose;
    }

    /**
     * 是否提交后自动返回, 默认false
     * @param bool $boolean
     * @return $this
     */
    public function setAutoBack($boolean = true)
    {
        $this->_autoBack = $boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getAutoBack()
    {
        return $this->_autoBack;
    }

    /**
     * 设置表单提交确认框
     * @param string $title 提示标题
     * @param string $content 提示内容
     * @return $this
     */
    public function setSubmitConfirm($title = '', $content = '是否确认提交?')
    {
        $this->_submitConfirm['show'] = true;
        $this->_submitConfirm['title'] = $title;
        $this->_submitConfirm['content'] = $content;

        return $this;
    }

    /**
     * @return array
     */
    public function getSubmitConfirm()
    {
        return $this->_submitConfirm;
    }

    /**
     * 设置额外的JS
     * @param array|string $js 额外的JS脚本
     * @return $this
     */
    public function setExtraJs($js)
    {
        $js = (array)$js;
        foreach ($js as &$value) {
            if (is_callable($value)) {
                $value = call_user_func($value);
            }
        }

        $this->_extraJs = array_merge($this->_extraJs, $js);
        return $this;
    }

    /**
     * 获取额外的JS
     * @return array
     */
    public function getExtraJs()
    {
        return $this->_extraJs;
    }

    /**
     * 设置额外的CSS
     * @param array|string $css 额外的CSS脚本
     * @return $this
     */
    public function setExtraCss($css)
    {
        $css = (array)$css;
        foreach ($css as &$value) {
            if (is_callable($value)) {
                $value = call_user_func($value);
            }
        }

        $this->_extraCss = array_merge($this->_extraCss, $css);
        return $this;
    }

    /**
     * 获取额外的CSS
     * @return array
     */
    public function getExtraCss()
    {
        return $this->_extraCss;
    }

    /**
     * 设置额外的静态资源包
     * @param string|array $extraAssetBundle 静态资源包
     * @return $this
     */
    public function setExtraAssetBundle($extraAssetBundle)
    {
        $extraAssetBundle = (array)$extraAssetBundle;

        // 检查
        foreach ($extraAssetBundle as $assetBundle) {
            $tempObj = new $assetBundle;
            if (!$tempObj instanceof AssetBundle) {
                throw new InvalidArgumentException($assetBundle . ' is not instance of ' . AssetBundle::class);
            }
        }

        $this->_extraAssetBundle = array_merge($this->_extraAssetBundle, $extraAssetBundle);
        return $this;
    }

    /**
     * 获取额外的静态资源包
     * @return array
     */
    public function getExtraAssetBundle()
    {
        return $this->_extraAssetBundle;
    }

    /**
     * 开始表单
     * @return null|string
     */
    public function beginForm()
    {
        if (empty($this->_pointcutBody[self::POS_HEAD])) {
            return null;
        }

        return implode("\n", $this->_pointcutBody[self::POS_HEAD]);
    }

    /**
     * 结束表单
     * @return null|string
     */
    public function endForm()
    {
        if (empty($this->_pointcutBody[self::POS_END])) {
            return null;
        }

        return implode("\n", $this->_pointcutBody[self::POS_END]);
    }

    /**
     * 格式化动态控件项
     * @param array $dynamicControls
     * @return mixed
     */
    protected function formatDynamicControl(array $dynamicControls)
    {
        $parentId = $dynamicControls['__id'];
        $__default = [];
        $__valTpl = [
            '__allowDefaultDrop' => true,
            '__allowDragSorting' => $dynamicControls['allowDragSorting'],
        ];
        $__jsItems = [];

        foreach ($dynamicControls['items'] as $field => &$item) {
            if (is_callable($item)) {
                $item = call_user_func($item);
                if (is_object($item) && $item instanceof Dcontrol) {
                    $item = $item->_toArray();
                }
            } elseif (is_object($item) && $item instanceof Dcontrol) {
                $item = $item->_toArray();
            }

            if (!is_array($item)) {
                throw new InvalidArgumentException('Expected parameter type is array, But the others are given. ');
            }

            // 生成控件ID
            $item['__id'] = $this->combineCtrlId($parentId, $field);

            // 生成值模板
            $__valTpl[$field] = isset($item['default']) ? $item['default'] : '';

            // JS选项
            $__jsItems[$field] = [
                'required' => !empty($item['required']) ? 1 : 0,
                'label' => isset($item['label']) ? $item['label'] : '',
            ];
        }

        // 格式化动态控价的默认值
        if (empty($dynamicControls['data'])) {
            $__default[] = $__valTpl;
        } else {
            // 格式化默认行
            foreach ($dynamicControls['data'] as $dataItem) {
                $tmpTpl = $__valTpl;
                foreach ($tmpTpl as $f => &$v) {
                    if (isset($dataItem[$f])) {
                        $v = $dataItem[$f];
                    }
                }

                // 根据配置设置是否允许删除默认行
                $tmpTpl['__allowDefaultDrop'] = $dynamicControls['allowDefaultDrop'];
                $__default[] = $tmpTpl;
            }
        }

        // 动态默认值
        $dynamicControls['__default'] = $__default;
        // 值模板
        $dynamicControls['__valTpl'] = $__valTpl;
        // JS选项
        $dynamicControls['__jsItem'] = $__jsItems;

        return $dynamicControls;
    }

    /**
     * 格式化表单控件项
     * @param array $controls
     * @return array
     */
    protected function formatControls(array $controls)
    {
        foreach ($controls as $field => &$item) {
            if (is_callable($item)) {
                $item = call_user_func($item);
                if (is_object($item) && $item instanceof Control) {
                    $item = $item->_toArray();
                }
            } elseif (is_object($item) && $item instanceof Control) {
                $item = $item->_toArray();
            }

            if (!is_array($item)) {
                throw new InvalidArgumentException('Expected parameter type is array, But the others are given. ');
            }

            // 生成控件ID
            $item['__id'] = $this->combineCtrlId($this->_groupField, $field);
            // 生成控件Scope模型
            $item['__scope'] = $this->combineCtrlScope($this->_groupField, $field);

            switch ($item['control']) {
                //触发器
                case 'trigger':
                    // 生成控件Scope模型
                    $item['__scope'] = '__' . $this->_groupField . '__' . $field;

                    //格式化触发器项
                    $item['triggerItems'] = $this->formatControls($item['triggerItems']);
                    $this->_formTrigger[] = $item;
                    $this->_formWatchScope = array_unique(array_merge($this->_formWatchScope, $item['watchScope']));

                    break;
                //值连接
                case 'value_link':
                    // 生成控件Scope模型
                    $item['__scope'] = '__' . $this->_groupField . '__' . $field;

                    //格式化值连接项
                    $item['sourceControl'] = $this->formatControls($item['sourceControl']);
                    foreach ($item['watchScope'] as &$value) {
                        $value = $this->combineCtrlScope($this->_groupField, $value);
                    }

                    $this->_formValueLink[] = $item;
                    $this->_formWatchScope = array_unique(array_merge($this->_formWatchScope, $item['watchScope']));

                    break;
                //动态SELECT
                case 'dynamic_select':
                    $this->_formDynamicSelect[] = [
                        '__id' => $item['__id'],
                        'dataUrl' => $item['dataUrl'],
                        'additionBtn' => $item['additionBtn'],
                    ];

                    break;
                //动态控件
                case 'dynamic_control':
                    //格式化动态控件项
                    $item = $this->formatDynamicControl($item);

                    $tmp = $item;
                    $tmp['__groupField'] = $this->_groupField;
                    $tmp['__field'] = $field;
                    $this->_formDynamicControl[] = $tmp;

                    break;
            }

            $tmpOption = [];
            isset($item['default']) && $tmpOption['default'] = $item['default'];
            isset($item['required']) && $tmpOption['required'] = $item['required'];
            isset($item['label']) && $tmpOption['label'] = $item['label'];
            if (!empty($tmpOption)) {
                //设置JS内容控件选项
                $this->_bodyJsOptions[$this->_groupField][$field] = $tmpOption;
            }
        }

        return $controls;
    }

    /**
     * 设置表单组控件
     * @param array $controls
     * @return $this
     */
    public function setFormControl(array $controls)
    {
        $controls = $this->formatControls($controls);

        // 注意：如果分组项出现重复，后者会覆盖前者。
        $this->_body[$this->_groupField] = [
            'title' => $this->_groupTitle,
            'layout' => $this->groupCol,
            'class' => $this->groupLayout['class'],
            'style' => $this->groupLayout['style'],
            'attribute' => $this->groupLayout['attribute'],
            'comment' => $this->groupComment,
            'controls' => $controls,
        ];

        return $this;
    }

    /**
     * 设置必填项标记风格
     * @param string $value
     * @return $this
     */
    public function setRequiredStyle($value = 'text')
    {
        if (!in_array($value, ['text', 'mark'])) {
            throw new InvalidArgumentException('The parameter must be one of [text] and [mark].');
        }

        $this->_requiredStyle = $value;
        return $this;
    }

    /**
     * @return string
     */
    public function getRequiredStyle()
    {
        return $this->_requiredStyle;
    }

    /**
     * 获取表单组内容
     * @return array
     */
    private function _getFormBody()
    {
        if (empty($this->_body)) {
            $defaultBody = [];
            $defaultBody[$this->_groupField] = [
                'title' => $this->_groupTitle,
                'layout' => $this->groupCol,
                'class' => $this->groupLayout['class'],
                'style' => $this->groupLayout['style'],
                'attribute' => $this->groupLayout['attribute'],
                'comment' => $this->groupComment,
                'controls' => [],
            ];

            return $defaultBody;
        }

        return $this->_body;
    }

    /**
     * 构建器名称
     * @return string
     */
    public function getName()
    {
        return 'form';
    }

    /**
     * @return string the view path that may be prefixed to a relative view name.
     */
    public function getViewPath()
    {
        return __DIR__;
    }

    /**
     * 表单渲染
     * @param boolean $onlyRenderContent 是否仅渲染内容
     * @return string
     * @throws \yii\base\InvalidConfigException
     */
    public function render($onlyRenderContent = false)
    {
        if ($this->title) {
            // 注册页面TITLE
            $this->_view->title = H5::encode(App::params('admin.brandName') . '|' . $this->title);
        }

        // 注册JS脚本
        $this->_view->registerJs($this->_resolveJsScript(), View::POS_END);

        // 注册额外CSS脚本
        if (!empty($this->extraCss)) {
            $this->_view->registerCss(implode("\n", $this->extraCss));
        }

        // 注册额外的AssetBundle
        if (!empty($this->extraAssetBundle)) {
            foreach ($this->extraAssetBundle as $assetBundle) {
                $this->_view->registerAssetBundle($assetBundle);
            }
        }

        // 渲染表单内容
        $content = $this->_view->render('view', [
            // view.php中参数注册
            'body' => $this->_getFormBody(),                                         // 表单组内容
            'rootLayout' => $this->rootLayout,                                       // 根布局
            'footerLayout' => $this->footerLayout,                                   // 尾部布局
            'requiredStyle' => $this->requiredStyle,                                 // 必填项标记风格
            'footerBtn' => $this->footerBtn,                                         // 尾部按钮

        ], $this);

        // 仅内容渲染
        if ($onlyRenderContent) {
            return $content;
        }

        // 局部布局渲染(不包含: 菜单和导航栏)
        if ($this->partial) {
            return $this->_view->renderFile($this->partialPath, ['content' => $content], $this);
        }

        // 主布局渲染(包含: 菜单和导航栏)
        return $this->_view->renderFile($this->mainPath, ['content' => $content], $this);
    }

    /**
     * 解析JS脚本
     * @return string
     */
    private function _resolveJsScript()
    {
        $jsScripts = $this->_view->render('app', [
            // app.php中参数注册
            'body' => $this->_getFormBody(),                                            // 表单内容
            'bodyJsOptions' => $this->_bodyJsOptions,                                   // 表单内容JS选项
            'autoClose' => $this->autoClose,                                            // 是否提交后自动关闭表单
            'autoBack' => $this->autoBack,                                              // 是否提交后自动返回
            'submitConfirm' => $this->submitConfirm,                                    // 设置表单提交确认框
            'submittedCalls' => $this->submittedCalls,                                  // 表单自动提交后的回调, 空数组代表不回调
            'extraJs' => $this->extraJs,                                                // 额外的JS脚本
            'formDynamicSelect' => $this->_formDynamicSelect,                           // 表单动态select
            'formValueLink' => $this->_formValueLink,                                   // 表单值连接选项
            'formTrigger' => $this->_formTrigger,                                       // 表单触发器选项
            'formWatchScope' => $this->_formWatchScope,                                 // 表单监听的字段模型
            'formDynamicControl' => $this->_formDynamicControl,                         // 表单动态控件选项
            'submittedRedirect' => $this->submittedRedirect,                            // 表单提交后重定向, 空数组代表无操作

        ], $this);

        return RegEx::js($jsScripts);
    }
}